package neptune.manager.allocator;

import it.luigiarpino.kernighanLin.LinKernighanAmmissibile;
import it.luigiarpino.kernighanLin.LinKernighanException;
import java.io.File;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import neptune.manager.model.cluster.Cluster;
import neptune.manager.model.cluster.PhysicalNode;
import neptune.manager.model.experiment.Experiment;
import neptune.manager.model.topology.Link;
import neptune.manager.model.topology.Node;
import neptune.manager.model.topology.Property;
import neptune.manager.model.topology.Topology;
import neptune.manager.storage.XMLClusterStorageFactory;
import neptune.manager.storage.XMLTopologyStorage;

/**
 * Fornisce la mappa per l'allocazione dell'esperimento secondo l'algoritmo di
 * Lin-Kernighan
 * 
 * @author Luigi Lorenzo Arpino [luigiarpino@yahoo.it]
 * 
 */
public class LKAllocationMap {

	public static void main(String[] args) throws Exception {
		String expId = "pluto";
		int maxVMsPerNode = 3;
		int maxBandwidth = 100;
		// /home/luigi/.emuPlugin/cluster.xml
		XMLClusterStorageFactory clusterStorage = new XMLClusterStorageFactory(
				"/home/luigi/.emuPlugin/cluster.xml");
		Cluster cluster = clusterStorage.getClusterStorage().getCluster();

		File file = new File("/home/luigi/.emuPlugin/topologies/" + expId
				+ ".topology.xml");
		XMLTopologyStorage storage = new XMLTopologyStorage(file, file);
		Topology topology = storage.getTopology();
		Experiment experiment = new Experiment("admin", expId);
		experiment.setTopologyWithoutControls(topology);

		LKAllocationMap lkMap = new LKAllocationMap(experiment, cluster,
				maxVMsPerNode, maxBandwidth);
		// Map<String, String[]> allocationMap = lkMap.getMap();
		System.out.println(lkMap.getMessaggio());
	}

	private Map<String, String[]> allocationMap;
	private String[] vms;
	private int[] partizionamento;
	private String messaggio;

	public LKAllocationMap(Experiment experiment, Cluster cluster,
			int maxVMsPerNode, int maxBandwidth) throws AllocationException {
		// Costruisco il vettore delle macchine virtuali dell'esperimento
		String expId = experiment.getExperimentId();
		Topology topology = experiment.getTopology();
		Node[] node = topology.getNodes();
		int numMacchineVirtuali = node.length;
		this.vms = new String[node.length];
		for (int i = 0; i < node.length; i++) {
			vms[i] = node[i].getId();
		}
		// Costruisco la lista delle macchine fisiche che non
		// contengono macchine virtuali
		PhysicalNode[] phyNodes = cluster.getNodes();
		if (phyNodes == null) {
			String msg = "\n Unable to allocate experiment '" + expId + "'."
					+ "\n The cluster is empty."
					+ "\n Please insert new physical machines!";
			throw new AllocationException(msg);
		}
		List<String> listaIDFreePhysicalNodes = new LinkedList<String>();
		List<String> listaNameFreePhysicalNodes = new LinkedList<String>();
		for (int i = 0; i < phyNodes.length; i++) {
			if (phyNodes[i].getHostedNodes().length == 0) {
				listaIDFreePhysicalNodes.add(phyNodes[i].getId());
				listaNameFreePhysicalNodes.add(phyNodes[i].getName());
			}
		}
		int macchineFisicheNecessarie = (int) Math
				.ceil((double) numMacchineVirtuali / (double) maxVMsPerNode);
		int macchineFisicheDisponibili = listaIDFreePhysicalNodes.size();
		// Verifico la disponibilita' delle macchine fisiche
		if (macchineFisicheNecessarie > macchineFisicheDisponibili) {
			String msg = "\n Unable to allocate experiment " + expId
					+ "\n There are only " + macchineFisicheDisponibili
					+ " free physical machine" + "\n The experiment " + expId
					+ " needs " + macchineFisicheNecessarie
					+ " physical machine";
			throw new AllocationException(msg);
		}
		// Costruisco la matrice di adiacenza
		// Inizializzazione della matrice di adiacenza
		int[][] matrice = new int[numMacchineVirtuali][numMacchineVirtuali];
		for (int row = 0; row < numMacchineVirtuali; row++) {
			for (int col = 0; col < numMacchineVirtuali; col++) {
				matrice[row][col] = 0;
			}
		}
		// della matrice di adiacenza
		Link[] links = topology.getLinks();
		for (int i = 0; i < links.length; i++) {
			String source = links[i].getSource().getNodeId();
			int indexSource = getIndexOfVirtualMachine(source);
			String dest = links[i].getDestination().getNodeId();
			int indexDest = getIndexOfVirtualMachine(dest);
			Property[] property = links[i].getProperties();
			for (int k = 0; k < property.length; k++) {
				if (property[k].getName().equalsIgnoreCase(Property.BANDWIDTH)) {
					int bandwith = getBandwidth(property[k].getValue());
					matrice[indexDest][indexSource] = bandwith;
					matrice[indexSource][indexDest] = bandwith;
				}
			}
		}

		int taglioMax = maxBandwidth;
		int maxNodi = maxVMsPerNode;

		try {
			// LinKernighanAmmissibile lk=new LinKernighanAmmissibile(matrice,
			// numeroPartizioni, taglioMax, maxNodi);
			LinKernighanAmmissibile lk = new LinKernighanAmmissibile(matrice,
					taglioMax, maxNodi);
			this.partizionamento = lk.getPartizionamento();
		} catch (LinKernighanException e) {
			String msg = "\n Unable to allocate experiment: '" + expId + "'"
					+ "\n" + e.getMessage();
			throw new AllocationException(msg);
		}

		// Calcolo la mappa di allocazione delle macchine virtuali
		this.allocationMap = new HashMap<String, String[]>(0);
		this.messaggio = new String("\nTopology mapping:\n");
		// for(int i=0; i< phyNodes.length ; i++)
		for (int i = 0; i < listaIDFreePhysicalNodes.size(); i++) {
			// String idPhysicalMachine = phyNodes[i].getId();
			String idPhysicalMachine = listaIDFreePhysicalNodes.get(i);
			String[] virtualMachines = getVMs(i);
			allocationMap.put(idPhysicalMachine, virtualMachines);
			// this.messaggio +=
			// "\nOn physical machine "+phyNodes[i].getName()+":";
			this.messaggio += "\nOn physical machine "
					+ listaNameFreePhysicalNodes.get(i) + ":";
			for (int k = 0; k < virtualMachines.length; k++) {
				this.messaggio += " " + virtualMachines[k];
			}
		}
		this.messaggio += "\n";
	}

	/**
	 * Fornisce il valore della banda in megabit
	 * 
	 * @param bandwidth
	 * @return
	 */
	private int getBandwidth(String bandwidth) {
		if (bandwidth.contains("kbps")) {
			bandwidth = bandwidth.replace("kbps", "");
		} else {

		}
		Integer bandaKbps = new Integer(bandwidth);
		int bandaMegabit = (int) Math.ceil((double) bandaKbps.intValue()
				/ (double) 1000);
		return bandaMegabit;
	}

	private int getIndexOfVirtualMachine(String vmName) {
		for (int i = 0; i < this.vms.length; i++) {
			if (vms[i].equalsIgnoreCase(vmName)) {
				return i;
			}
		}
		return -1;
	}

	public Map<String, String[]> getMap() {
		return this.allocationMap;
	}

	/**
	 * Restituisce la descrizione dell'allocazione effettuata
	 * 
	 * @return
	 */
	public String getMessaggio() {
		return this.messaggio;
	}

	/**
	 * Restituisce il numero di macchine virtuali da allocare sulla macchina
	 * fisica di indice index
	 * 
	 * @param index
	 *            l'indice della macchina fisica
	 */
	private int getNumOfVMs(int index) {
		int count = 0;
		for (int i = 0; i < partizionamento.length; i++) {
			if (partizionamento[i] == index) {
				count++;
			}
		}
		return count;
	}

	/**
	 * Restituisce il vettore delle macchine virtuali da allocare sulla machina
	 * fisica di indice index
	 * 
	 * @param index
	 * @return
	 */
	private String[] getVMs(int index) {
		String[] result = new String[getNumOfVMs(index)];
		int k = 0;
		for (int i = 0; i < partizionamento.length; i++) {
			if (partizionamento[i] == index) {
				result[k] = this.vms[i];
				k++;
			}
		}
		return result;
	}
}
